    // Copyright (C) 2003  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_HASH_TABLE_KERNEl_2_
    #define DLIB_HASH_TABLE_KERNEl_2_

    #include "hash_table_kernel_abstract.h"
    #include "../general_hash/general_hash.h"
    #include "../algs.h"
    #include "../interfaces/map_pair.h"
    #include "../interfaces/enumerable.h"
    #include "../interfaces/remover.h"
    #include "../assert.h"
    #include "../serialize.h"
    #include <functional>

    namespace dlib
    {

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager = default_memory_manager,
            typename compare = std::less<domain>
            >
        class hash_table_kernel_2 : public enumerable<map_pair<domain,range> >,
                                    public pair_remover<domain,range>
        {

            /*!
                REQUIREMENTS ON bst_base
                    bst_base is instantiated with domain and range and
                    implements binray_search_tree/binary_search_tree_kernel_abstract.h

                 INITIAL VALUE
                    hash_size == 0
                    table == pointer to an array of num_of_buckets bst_base objects
                    num_of_buckets == the number of buckets in the hash table
                    current_bucket == 0
                    at_start_ == true

                CONVENTION
                    current_element_valid() == (current_bucket != 0)
                    element() == current_bucket->element()
                    at_start_ == at_start()

                    mask == num_of_buckets-1

                    for all integers i where &table[i] != current_bucket
                        table[i].at_start() == true

                    hash_size = size() == the number of elements in the hash_table and
                    table == pointer to an array of num_of_buckets bst_base objects
                    num_of_buckets == the number of buckets in the hash table and
                    the elements in this hash table are stored in the bst_base objects in the
                    array table

            !*/

            public:

                typedef domain domain_type;
                typedef range range_type;
                typedef compare compare_type;
                typedef mem_manager mem_manager_type;

                explicit hash_table_kernel_2(
                    unsigned long expnum
                );

                virtual ~hash_table_kernel_2(
                )
                { pool.deallocate_array(table); }

                void clear(
                );

                unsigned long count (
                    const domain& item
                ) const;

                inline void add (
                    domain& d,
                    range& r
                );

                void destroy (
                    const domain& d
                );

                void remove (
                    const domain& d,
                    domain& d_copy,
                    range& r
                );

                const range* operator[] (
                    const domain& item
                ) const;

                range* operator[] (
                    const domain& item
                );

                inline void swap (
                    hash_table_kernel_2& item
                );

                // functions from the remover interface
                void remove_any (
                    domain& d,
                    range& r
                );

                // functions from the enumerable interface
                inline size_t size (
                ) const;

                inline bool at_start (
                ) const;

                inline void reset (
                ) const;

                bool current_element_valid (
                ) const;

                inline const map_pair<domain,range>& element (
                ) const;

                inline map_pair<domain,range>& element (
                );

                bool move_next (
                ) const;

            private:

                // data members
                typename mem_manager::template rebind<bst_base>::other pool;
                unsigned long mask;
                unsigned long hash_size;
                unsigned long num_of_buckets;
                bst_base* table;
                general_hash<domain> hash;
                mutable bst_base* current_bucket;
                mutable bool at_start_;
                compare comp;

                // restricted functions
                hash_table_kernel_2(hash_table_kernel_2&);
                hash_table_kernel_2& operator=(hash_table_kernel_2&);

        };

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        inline void swap (
            hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>& a,
            hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>& b
        ) { a.swap(b); }

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        void deserialize (
            hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>& item,
            std::istream& in
        )
        {
            try
            {
                item.clear();
                unsigned long size;
                deserialize(size,in);
                domain d;
                range r;
                for (unsigned long i = 0; i < size; ++i)
                {
                    deserialize(d,in);
                    deserialize(r,in);
                    item.add(d,r);
                }
            }
            catch (serialization_error& e)
            {
                item.clear();
                throw serialization_error(e.info + "\n   while deserializing object of type hash_table_kernel_2");
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // member function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        hash_table_kernel_2(
            unsigned long expnum
        ) :
            hash_size(0),
            current_bucket(0),
            at_start_(true)
        {

            num_of_buckets = 1;
            while (expnum != 0)
            {
                --expnum;
                num_of_buckets <<= 1;
            }
            mask = num_of_buckets-1;

            table = pool.allocate_array(num_of_buckets);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        clear(
        )
        {
            if (hash_size != 0)
            {
                hash_size = 0;
                for (unsigned long i = 0; i < num_of_buckets; ++i)
                    table[i].clear();
            }
            // reset the enumerator
            reset();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        size_t hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        size(
        ) const
        {
            return hash_size;
        }
    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        unsigned long hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        count(
            const domain& item
        ) const
        {
            return table[hash(item)&mask].count(item);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        destroy(
            const domain& item
        )
        {
            table[hash(item)&mask].destroy(item);
            --hash_size;

            // reset the enumerator
            reset();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        add(
            domain& d,
            range& r
        )
        {
            table[hash(d)&mask].add(d,r);
            ++hash_size;

            // reset the enumerator
            reset();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        remove(
            const domain& d,
            domain& d_copy,
            range& r
        )
        {
            table[hash(d)&mask].remove(d,d_copy,r);
            --hash_size;

            // reset the enumerator
            reset();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        remove_any(
            domain& d,
            range& r
        )
        {
            unsigned long i = 0;
            while (table[i].size() == 0)
            {
                ++i;
            }
            table[i].remove_any(d,r);
            --hash_size;

            // reset the enumerator
            reset();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        const range* hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        operator[](
            const domain& d
        ) const
        {
            return table[hash(d)&mask][d];
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        range* hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        operator[](
            const domain& d
        )
        {
            return table[hash(d)&mask][d];
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        swap(
            hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>& item
        )
        {
            pool.swap(item.pool);
            exchange(mask,item.mask);
            exchange(hash_size,item.hash_size);
            exchange(num_of_buckets,item.num_of_buckets);
            exchange(table,item.table);
            exchange(current_bucket,item.current_bucket);
            exchange(at_start_,item.at_start_);
            exchange(comp,item.comp);
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // enumerable function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        bool hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        at_start (
        ) const
        {
            return at_start_;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        reset (
        ) const
        {
            at_start_ = true;
            if (current_bucket != 0)
            {
                current_bucket->reset();
                current_bucket = 0;
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        bool hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        current_element_valid (
        ) const
        {
            return (current_bucket != 0);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        const map_pair<domain,range>& hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        element (
        ) const
        {
            return current_bucket->element();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        map_pair<domain,range>& hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        element (
        )
        {
            return current_bucket->element();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename bst_base,
            typename mem_manager,
            typename compare
            >
        bool hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
        move_next (
        ) const
        {
            if (at_start_)
            {
                at_start_ = false;
                // if the queue is empty then there is nothing to do
                if (hash_size == 0)
                {
                    return false;
                }
                else
                {
                    // find the first element in the hash table
                    current_bucket = table;
                    while (current_bucket->size() == 0)
                    {
                        ++current_bucket;
                    }

                    current_bucket->move_next();

                    return true;
                }
            }
            else
            {
                // if we have already enumerated every element
                if (current_bucket == 0)
                {
                    return false;
                }
                else
                {
                    if (current_bucket->move_next())
                    {
                        // if there is another element in this current bucket then use that
                        return true;
                    }
                    else
                    {
                        // find the next bucket
                        bst_base* end = table + num_of_buckets;
                        current_bucket->reset();

                        while (true)
                        {
                            ++current_bucket;
                            // if we ran out of buckets and didn't find anything
                            if (current_bucket == end)
                            {
                                current_bucket = 0;
                                return false;
                            }
                            if (current_bucket->size() > 0)
                            {
                                current_bucket->move_next();
                                return true;
                            }
                        }
                    }
                }
            }
        }

    // ----------------------------------------------------------------------------------------

    }

    #endif // DLIB_HASH_TABLE_KERNEl_2_